package gitee.derumin.item_fucker;

import gitee.derumin.lib_for_derumin.logger.DeruminLogger;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
import net.minecraft.block.Block;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.FallingBlockEntity;
import net.minecraft.entity.ItemEntity;
import net.minecraft.item.Item;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.text.Text;
import net.minecraft.util.math.Vec3d;
import net.minecraft.world.World;

import java.util.*;

import static gitee.derumin.item_fucker.Item_fucker.CONFIG;

public class Court {
    private static final DeruminLogger LOGGER = DeruminLogger.getLogger();
    private static final Map<Item, Count> TRACKED_ITEM = new HashMap<>();
    private static final Map<Block, Count> TRACKED_BLOCK = new HashMap<>();
    private static final Map<Item, Convicts> CRIMINAL_ITEM = new HashMap<>();
    private static final Map<Block, Convicts> CRIMINAL_BLOCK = new HashMap<>();

    public static void init(){
        ServerTickEvents.START_SERVER_TICK.register(Court::tick);
    }

    public static void tick(MinecraftServer server){
        server.getWorlds().forEach(world -> {
            List<? extends ItemEntity> itemList =world.getEntitiesByType(EntityType.ITEM, entity -> !entity.isRemoved());
            itemList.forEach(Court::censorItem);
            List<? extends FallingBlockEntity> blockList = world.getEntitiesByType(EntityType.FALLING_BLOCK, entity -> !entity.isRemoved());
            blockList.forEach(Court::censorBlock);
        });
        Iterator<Map.Entry<Item, Count>> trackedItem = TRACKED_ITEM.entrySet().iterator();
        while (trackedItem.hasNext()){
            Map.Entry<Item, Count> entry = trackedItem.next();
            Count count = entry.getValue();
            Item item = entry.getKey();
            if (count.isExcessive(CONFIG.maxItemEntities) && !CRIMINAL_ITEM.containsKey(item)){
                if (count.maybeStorageBombed()){
                    LOGGER.warn("[Item Fucker]Item: " + item.toString() + " is excessive! Maybe a Storehouse is bombed!", DeruminLogger.AnnounceType.NONE);
                    server.getPlayerManager().getPlayerList().forEach(serverPlayerEntity
                            -> serverPlayerEntity.sendMessageToClient(
                            Text.translatable("item_fucker.announce.1")
                                    .append(Text.translatable(item.getTranslationKey()))
                                    .append(Text.translatable("item_fucker.announce.2")), false));
                }else {
                    LOGGER.warn("[Item Fucker]Item: " + item.toString() + " is excessive!", DeruminLogger.AnnounceType.NONE);
                    server.getPlayerManager().getPlayerList().forEach(serverPlayerEntity
                            -> serverPlayerEntity.sendMessageToClient(
                            Text.translatable("item_fucker.announce.3")
                                    .append(Text.translatable(item.getTranslationKey()))
                                    .append(Text.translatable("item_fucker.announce.4")), false));
                }
                CRIMINAL_ITEM.put(item, new Convicts(400));
            }
            count.tick();
            if (count.isOvertime()){
                trackedItem.remove();
            }
        }
        Iterator<Map.Entry<Block, Count>> trackedBlock = TRACKED_BLOCK.entrySet().iterator();
        while (trackedBlock.hasNext()){
            Map.Entry<Block, Count> entry = trackedBlock.next();
            Count count = entry.getValue();
            Block block = entry.getKey();
            if (count.isExcessive(CONFIG.maxFallingBlocks) && !CRIMINAL_BLOCK.containsKey(block)){
                CRIMINAL_BLOCK.put(block, new Convicts(200));
                LOGGER.warn("[Item Fucker]Falling Block: " + block.toString() + "is excessive!", DeruminLogger.AnnounceType.NONE);
                server.getPlayerManager().getPlayerList().forEach(serverPlayerEntity
                        -> serverPlayerEntity.sendMessageToClient(
                                Text.translatable("item_fucker.announce.5")
                                        .append(Text.translatable(block.getTranslationKey()))
                                        .append(Text.translatable("item_fucker.announce.6")), false));
            }
            count.tick();
            if (count.isOvertime()){
                trackedBlock.remove();
            }
        }
        CRIMINAL_ITEM.entrySet().removeIf(entry -> entry.getValue().tickAndPunish());
        CRIMINAL_BLOCK.entrySet().removeIf(entry -> entry.getValue().tickAndPunish());
    }

    public static void censorItem(ItemEntity entity){
        Item item = entity.getStack().getItem();
        if (!TRACKED_ITEM.containsKey(item)){
            TRACKED_ITEM.put(item, new Count());
        }else {
            TRACKED_ITEM.get(item).count();
        }
        if (CRIMINAL_ITEM.containsKey(item)){
            CRIMINAL_ITEM.get(item).addOneCrime((ServerWorld) entity.getWorld(), entity.getPos());
            entity.discard();
        }
    }

    public static void censorBlock(FallingBlockEntity entity){
        Block block = entity.getBlockState().getBlock();
        if (!TRACKED_BLOCK.containsKey(block)){
            TRACKED_BLOCK.put(block, new Count());
        }else {
            TRACKED_BLOCK.get(block).count();
        }
        if (CRIMINAL_BLOCK.containsKey(block)){
            CRIMINAL_BLOCK.get(block).addOneCrime((ServerWorld) entity.getWorld(), entity.getPos());
            entity.discard();
        }
    }

    static class Count {
        private int lifetime;
        private int number;
        private int oldNumber;

        Count(){
            this.lifetime = 200;
            this.number = 1;
            this.oldNumber = 0;
        }

        void tick(){
            if (this.number != 0){
                this.lifetime =200;
            }else {
                this.lifetime --;
            }
            this.oldNumber = this.number;
            this.number = 0;
        }

        boolean isExcessive(int limit){
            return this.number > limit;
        }

        boolean maybeStorageBombed(){
            return this.number > this.oldNumber + 200;
        }

        boolean isOvertime(){
            return this.lifetime <=0;
        }

        void count(){
            this.number ++;
        }
    }

    static class Convicts {
        private int timeDelay;
        private final List<CrimeScene> crimeScenes;

        Convicts(int timeDelay){
            this.timeDelay = timeDelay;
            this.crimeScenes = new ArrayList<>();
        }

        void addOneCrime(ServerWorld world, Vec3d pos){
            if (this.timeDelay <= 200) {
                boolean added = false;
                for (CrimeScene crimeScene : this.crimeScenes) {
                    if (crimeScene.addOneCrime(world, pos)) {
                        added = true;
                        break;
                    }
                }
                if (!added) {
                    this.crimeScenes.add(new CrimeScene(world, pos));
                }
            }
        }

        boolean tickAndPunish(){
            if (this.timeDelay > 0){
                this.timeDelay --;
                return false;
            }
            int i = 1;
            for (CrimeScene crimeScene : crimeScenes) {
                crimeScene.punish(i);
                i++;
            }
            return true;
        }

        static class CrimeScene{
            private final ServerWorld world;
            private Vec3d pos;
            private int times;

            CrimeScene(ServerWorld world, Vec3d pos){
                this.world = world;
                this.pos = pos;
                this.times = 1;
            }

            boolean addOneCrime(ServerWorld world, Vec3d pos){
                if (world.equals(this.world) && pos.isInRange(this.pos, 10)){
                    this.pos = new Vec3d(
                            (this.pos.getX() * this.times + pos.getX())/(this.times + 1),
                            (this.pos.getY() * this.times + pos.getY())/(this.times + 1),
                            (this.pos.getZ() * this.times + pos.getZ())/(this.times + 1));
                    this.times ++;
                    return true;
                }else {
                    return false;
                }
            }

            void punish(int index){
                if (this.times < 3){
                    return;
                }
                if (this.times > 10){
                    this.times = 10;
                }
                List<ServerPlayerEntity> players = this.world.getPlayers();
                players.sort((o1, o2) -> Double.compare(o1.squaredDistanceTo(this.pos), o2.squaredDistanceTo(this.pos)));
                StringBuilder playerDis = new StringBuilder("\n");
                for (ServerPlayerEntity player : players){
                    Vec3d playerPos = player.getPos();
                    playerDis.append(player.getName().getString())
                            .append(": distance:")
                            .append(Math.sqrt(player.squaredDistanceTo(this.pos)))
                            .append(", position:[")
                            .append(playerPos.getX())
                            .append(",")
                            .append(playerPos.getY())
                            .append(",")
                            .append(playerPos.getZ())
                            .append("]\n");
                }
                LOGGER.record("[Item Fucker]Scene index:" + index + ", position: ["
                        + this.pos.getX() + "," + this.pos.getY() + "," + this.pos.getZ()
                        + "], nearby players: " + playerDis);
                if (CONFIG.doPunishment) {
                    while (this.times > 0) {
                        this.times--;
                        this.world.createExplosion(
                                null,
                                this.pos.getX() + Math.random() * 20 - 10,
                                this.pos.getY() + Math.random() * 20 - 10,
                                this.pos.getZ() + Math.random() * 20 - 10,
                                6.0f,
                                true,
                                World.ExplosionSourceType.BLOCK);
                    }
                }
            }
        }
    }
}
